package com.ampaiva.organizescreen.controller;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import android.graphics.Point;
import android.graphics.Region;
import android.util.Log;
import android.view.SurfaceHolder;

import com.ampaiva.organizescreen.activity.PuzzlePanel;
import com.ampaiva.organizescreen.activity.PuzzleThread;
import com.ampaiva.organizescreen.dao.PuzzleDAO;
import com.ampaiva.organizescreen.model.Connection;
import com.ampaiva.organizescreen.model.Node;
import com.ampaiva.organizescreen.model.Puzzle;
import com.ampaiva.pattern.fsm.FinalStateMachine;
import com.ampaiva.pattern.fsm.FinalStateMachine.Action;

public class PuzzleController {
	private static final String TAG = PuzzleController.class.getSimpleName();
	private PuzzlePanel view;
	private MoveList moveList;
	private PuzzleThread thread;

	private enum PuzzleEvents {
		PE_START, PE_PAUSE, PE_FINISH;
	}

	private final FinalStateMachine<PuzzleController, Puzzle, Puzzle.PuzzleStates, PuzzleEvents> fsm = new FinalStateMachine<PuzzleController, Puzzle, Puzzle.PuzzleStates, PuzzleEvents>(
	        this);

	public void onSurfaceCreated(PuzzlePanel view) {
		this.view = view;
		fsm.processEvent(PuzzleDAO.getPuzzle(), PuzzleEvents.PE_START);
	}

	@Action
	public void onStart(Puzzle puzzle, Puzzle.PuzzleStates state, PuzzleEvents event) {
		if (event == PuzzleEvents.PE_START) {
			initPuzzle(puzzle);
			start();
		}

	}

	private void initPuzzle(Puzzle puzzle) {
		List<Node> nodes = generateNodes(puzzle.getColumns());
		puzzle.setNodes(nodes);
		setDimensions(puzzle.getColumns(), nodes);
		moveList = new MoveList(nodes.size());
		for (Node node : nodes)
			moveList.append(node);
		List<Connection> connections = generateConnections(puzzle.getColumns(), nodes);
		puzzle.setConnections(connections);
		for (int j = 0; j < 2; j++)
			for (int i = 0; i < nodes.size(); i++)
				moveList.put(new Random().nextInt(nodes.size()), nodes.get(i));
		for (Node node : nodes)
			node.setId(node.getPosition() + 1);
		view.setPuzzle(puzzle);
	}

	public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		Log.d(TAG, "SurfaceChanged => width=" + width + ", height=" + height);
		Puzzle puzzle = PuzzleDAO.getPuzzle();
		List<Node> nodes = puzzle.getNodes();
		setDimensions(puzzle.getColumns(), nodes);
	}

	private Puzzle getPuzzle() {
		return view.getPuzzleDrawer().getPuzzle();
	}

	public static List<Node> generateNodes(int columns) {
		List<Node> nodes = new ArrayList<Node>(columns * columns);
		for (int i = 0; i < columns * columns; i++) {
			Node node = new Node(i, columns);
			nodes.add(node);
		}
		return nodes;
	}

	public static List<Connection> generateConnections(int columns, List<Node> nodes) {
		List<Connection> connections = new ArrayList<Connection>(nodes.size() - 1);
		// 0 -> 1 -> 2 -> 3
		// ...............|
		// ...............v
		// 4 <- 5 <- 6 <- 7
		// |
		// v
		// 8 -> 9 -> 10 ->11
		// ...............|
		// ...............v
		// 12<- 13<- 14<- 15

		// 3-> 7
		// 11->15
		for (int i = columns - 1; i < nodes.size() - 1; i += 2 * columns) {
			if (i + columns <= nodes.size() - 1) {
				Connection connection = new Connection(nodes.get(i), nodes.get(i + columns));
				connections.add(connection);
			}
		}
		// 4-> 8
		for (int i = columns; i < nodes.size() - 1; i += 2 * columns) {
			if (i + columns <= nodes.size() - 1) {
				Connection connection = new Connection(nodes.get(i), nodes.get(i + columns));
				connections.add(connection);
			}
		}

		// 0 -> 1 -> 2 -> 3
		// 8 -> 9 -> 10 ->11
		for (int i = 0; i < nodes.size() - 1; i += 2 * columns) {
			for (int j = 0; j < columns - 1; j++)
				if (i + j + 1 <= nodes.size() - 1) {
					Connection connection = new Connection(nodes.get(i + j), nodes.get(i + j + 1));
					connections.add(connection);
				}
		}
		// 4 <- 5 <- 6 <- 7
		// 12<- 13<- 14<- 15
		for (int i = columns; i < nodes.size() - 1; i += 2 * columns) {
			for (int j = 0; j < columns - 1; j++)
				if (i + j + 1 <= nodes.size() - 1) {
					Connection connection = new Connection(nodes.get(i + j + 1), nodes.get(i + j));
					connections.add(connection);
				}
		}

		return connections;
	}

	private void setDimensions(int columns, List<Node> nodes) {
		int totalEdge = Math.min(view.getWidth(), view.getHeight());
		int margin = totalEdge / columns / 2; // 50%
		int nodeEdge = (totalEdge - (columns + 1) * margin) / columns;

		for (Node node : nodes) {
			int width = columns * (nodeEdge + margin);
			int heigth = (nodes.size() / columns) * (nodeEdge + margin);
			Point point = new Point(new Random().nextInt(width), new Random().nextInt(heigth));
			Region parent = new Region(0, 0, width, heigth);
			node.setDimensions(margin, margin, parent, point.x, point.y, point.x + nodeEdge, point.y + nodeEdge);
		}
	}

	public void handleActionDown(Node node, int x, int y) {
		boolean contains = node.getRegion().contains(x, y);
		if (contains)
			node.setTouched(new Point(x, y));
		else
			node.setTouched(null);
	}

	public void onActionDown(int x, int y) {
		Log.d(TAG, "DW => Coords: x=" + x + ", y=" + y);
		for (Node node : getPuzzle().getNodes()) {
			handleActionDown(node, x, y);
			if (node.isTouched()) {
				Log.d(TAG, "Node touched at " + x + ", y=" + y);
				break;
			}
		}
		handleMenu(x, y);
	}

	private void handleActionMove(Node node, int x, int y) {
		if (node.isTouched()) {
			int dx = x - node.getTouched().x;
			int dy = y - node.getTouched().y;
			node.getTouched().set(x, y);

			node.move(dx, dy);
		}
	}

	public void onActionMove(int x, int y) {
		Log.d(TAG, "MV => Coords: x=" + x + ",y=" + y);
		for (Node node : getPuzzle().getNodes()) {
			handleActionMove(node, x, y);
		}
	}

	public void onActionUp(int x, int y) {
		Log.d(TAG, "UP => Coords: x=" + x + ",y=" + y);
		// touch was released
		for (Node node : getPuzzle().getNodes())
			handleActionUp(node, x, y);
		if (thread == null)
			start();
		else {
			alignNodes();
			if (checkFinish()) {
				// Workaround
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
				}
				stop();
				view.showEnd(PuzzleDAO.getPuzzle().getStartTime());
			}
		}
	}

	public void handleActionUp(Node node, int x, int y) {
		if (node.getTouched() != null) {
			Log.d(TAG, "Untouched: order=" + node.getOrder() + " position=" + node.getPosition());
			Puzzle puzzle = PuzzleDAO.getPuzzle();
			puzzle.setMoveCount(puzzle.getMoveCount() + 1);
			view.setCount(puzzle.getMoveCount());
		}
		node.setTouched(null);
		moveList.put(node.getPosition(), node);
	}

	private void handleMenu(float x, float y) {
		// check if in the lower part of the screen we exit
		if (y < view.getHeight() - 50)
			return;
		if (x > view.getWidth() / 2) {
			view.setFinished();
		}
	}

	private boolean checkFinish() {
		for (Node node : getPuzzle().getNodes())
			if (!node.isInRightPosition())
				return false;
		return true;
	}

	private void alignNodes() {
		for (Node node : getPuzzle().getNodes())
			node.align();
	}

	private void start() {
		// at this point the surface is created and
		// we can safely start the game loop
		// create the game loop thread
		PuzzleDAO.getPuzzle().setStartTime(new Date().getTime());

		thread = new PuzzleThread(view.getHolder(), view);
		thread.setRunning(true);
		thread.start();
	}

	private void stop() {
		thread.setRunning(false);
		thread = null;
	}

	public void onSurfaceDestroyed() {
		Log.d(TAG, "Surface is being destroyed");
		// tell the thread to shut down and wait for it to finish
		// this is a clean shutdown
		boolean retry = true;
		while (retry) {
			try {
				thread.join();
				retry = false;
			} catch (InterruptedException e) {
				// try again shutting down the thread
			}
		}
		Log.d(TAG, "Thread was shut down cleanly");
	}
}
